package edu.udo.sopra10.chaturaji.entities.pieces;

import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertNotNull;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableMap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import edu.udo.sopra10.chaturaji.entities.Player;
import edu.udo.sopra10.chaturaji.entities.Square;
import edu.udo.sopra10.chaturaji.entities.coordinates.Movement;
import edu.udo.sopra10.chaturaji.entities.coordinates.Position;
import edu.udo.sopra10.chaturaji.util.CloneUtil;

/**
 * A pawn is one of the Chaturaji {@link Piece}s. It is worth a single points.
 * Players start with four pawns in their first row. Pawns move exactly one
 * square away from their player. They, however, can not make captures in that
 * direction. To capture a piece, they must move one square along a diagonal
 * leading away from their player; they capture the piece they land on.
 * 
 * @author Simon Dierl
 */
public final class Pawn extends Piece {
	/**
	 * The captures a black pawn is theoretically capable of performing.
	 */
	private static final Collection<Movement> BLACK_CAPTURES = unmodifiableCollection(asList(
			new Movement((byte) -1, (byte) 1), new Movement((byte) 1, (byte) 1)));

	/**
	 * The movement a black pawn is theoretically capable of performing.
	 */
	private static final Movement BLACK_MOVEMENT = new Movement((byte) 0,
			(byte) 1);

	/**
	 * All captures a pawn is theoretically capable of performing.
	 */
	private static final Map<Byte, Collection<Movement>> CAPTURES;

	/**
	 * The captures a green pawn is theoretically capable of performing.
	 */
	private static final Collection<Movement> GREEN_CAPTURES = unmodifiableCollection(asList(
			new Movement((byte) -1, (byte) -1), new Movement((byte) 1,
					(byte) -1)));

	/**
	 * The movement a green pawn is theoretically capable of performing.
	 */
	private static final Movement GREEN_MOVEMENT = new Movement((byte) 0,
			(byte) -1);

	/**
	 * All movements a pawn is theoretically capable of performing.
	 */
	private static final Map<Byte, Movement> MOVEMENTS;

	/**
	 * The captures a red pawn is theoretically capable of performing.
	 */
	private static final Collection<Movement> RED_CAPTURES = unmodifiableCollection(asList(
			new Movement((byte) -1, (byte) -1), new Movement((byte) -1, (byte) 1)));

	/**
	 * The movement a red pawn is theoretically capable of performing.
	 */
	private static final Movement RED_MOVEMENT = new Movement((byte) -1,
			(byte) 0);

	/**
	 * The captures a yellow pawn is theoretically capable of performing.
	 */
	private static final Collection<Movement> YELLOW_CAPTURES = unmodifiableCollection(asList(
			new Movement((byte) 1, (byte) -1), new Movement((byte) 1, (byte) 1)));

	/**
	 * The movement a yellow pawn is theoretically capable of performing.
	 */
	private static final Movement YELLOW_MOVEMENT = new Movement((byte) 1,
			(byte) 0);

	static {
		final Map<Byte, Movement> movements = new HashMap<Byte, Movement>();
		movements.put((byte) 0, YELLOW_MOVEMENT);
		movements.put((byte) 1, BLACK_MOVEMENT);
		movements.put((byte) 2, RED_MOVEMENT);
		movements.put((byte) 3, GREEN_MOVEMENT);
		MOVEMENTS = unmodifiableMap(movements);

		final Map<Byte, Collection<Movement>> captures = new HashMap<Byte, Collection<Movement>>();
		captures.put((byte) 0, YELLOW_CAPTURES);
		captures.put((byte) 1, BLACK_CAPTURES);
		captures.put((byte) 2, RED_CAPTURES);
		captures.put((byte) 3, GREEN_CAPTURES);
		CAPTURES = unmodifiableMap(captures);
	}

	/**
	 * Create a new {@link Pawn}.
	 * 
	 * @param owner
	 *            the player owning the pawn, must not be {@code null}.
	 *            Determines move direction.
	 * @param square
	 *            the square the pawn stands on, may be {@code null} to indicate
	 *            a captured piece.
	 */
	public Pawn(final Player owner, final Square square) {
		super((byte) 1, owner, square);
		this.pieceValue = 20;
	}

	@Override
	public Actions getPossibleActions(final Square[][] board,
			final Position currentPosition) {
		assertNotNull(board, "board");
		assertNotNull(currentPosition, "currentPosition");

		final Collection<Position> emptyPositions = emptyList();

		final Collection<Position> possibleMoves;
		final Collection<Position> possibleCaptures = new ArrayList<Position>(2);
		final Collection<Position> possiblePawnPromotions;

		final byte thisTurnOrderPosition = owner.getTurnOrderPosition();
		boolean isPromoting = false;

		final Movement thisMovement = MOVEMENTS.get(thisTurnOrderPosition);

		final Position movePosition = CloneUtil.clone(currentPosition);
		try {
			movePosition.shift(thisMovement);
			if (board[movePosition.getX()][movePosition.getY()].getPiece() == null) {
				possibleMoves = singletonList(movePosition);
			} else {
				possibleMoves = emptyPositions;
			}
			try {
				final Position nextMovePosition = CloneUtil.clone(movePosition);
				nextMovePosition.shift(thisMovement);
			} catch (final IllegalArgumentException e) {
				isPromoting = true;
			}
		} catch (final IllegalArgumentException e) {
			return new Actions(emptyPositions, emptyPositions, null,
					emptyPositions);
		}

		for (final Movement captureMovement : CAPTURES
				.get(thisTurnOrderPosition)) {
			final Position capturePosition = CloneUtil.clone(currentPosition);
			try {
				capturePosition.shift(captureMovement);
			} catch (final IllegalArgumentException e) {
				continue;
			}
			
			final Piece piece = board[capturePosition.getX()][capturePosition.getY()].getPiece();
			if ( piece != null && !piece.getOwner().equals( owner ) ) {
				possibleCaptures.add(capturePosition);
			}
		}
		
		if (isPromoting) {
			possiblePawnPromotions = new ArrayList<Position>(
					possibleCaptures.size() + possibleMoves.size());
			possiblePawnPromotions.addAll(possibleCaptures);
			possiblePawnPromotions.addAll(possibleMoves);
		} else {
			possiblePawnPromotions = emptyList();
		}

		return (new Actions(possibleMoves, possibleCaptures, null,
				possiblePawnPromotions));
	}
	
	/**
	 * Returns true if the pawn is on a position where it can promote.
	 * 
	 * @return true if a promotion is possible.
	 */
	public boolean canPromote ()
	{
		final Position position = CloneUtil.clone( square.getPosition() );
		
		final byte thisTurnOrderPosition = owner.getTurnOrderPosition();
		final Movement thisMovement = MOVEMENTS.get(thisTurnOrderPosition);
		
		try {
			position.shift( thisMovement );
		} catch ( IllegalArgumentException e ) {
			return true;
		}
		
		return false;
	}
}